Skip to content

MAINT: Standardize system prompts on prepended_conversation#2040

Open
adrian-gavrila wants to merge 12 commits into
microsoft:mainfrom
adrian-gavrila:adrian-gavrila/standardize-system-prompt
Open

MAINT: Standardize system prompts on prepended_conversation#2040
adrian-gavrila wants to merge 12 commits into
microsoft:mainfrom
adrian-gavrila:adrian-gavrila/standardize-system-prompt

Conversation

@adrian-gavrila

@adrian-gavrila adrian-gavrila commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Description

Standardizes objective-target system prompts on prepended_conversation, the existing path that supports one or more system messages and composes with seeded conversation history.

SingleTurnAttackContext.system_prompt was exposed but never consumed, so setting it silently did nothing. This PR keeps the field temporarily for compatibility, emits a DeprecationWarning when it is used, and schedules its removal for 0.17.0.

Callers should instead use:

prepended_conversation=[
    Message.from_system_prompt("You are a helpful assistant.")
]

For multiple system prompts, this PR adds Message.from_system_prompts(...), which returns the same list[Message] callers could build manually.

Changes

  • Deprecates the unused SingleTurnAttackContext.system_prompt field.
  • Adds Message.from_system_prompts(...).
  • Documents prepended_conversation as the standard system-prompt channel.
  • Adds coverage for the deprecation warning and message builder.

Implements ADO #9697’s framework-standardization track. The CoPyRIT GUI work was delivered separately in #2056.

Validation

  • All PR checks pass.
  • The paired documentation .py and .ipynb files were synchronized and executed.

adrian-gavrila and others added 3 commits June 17, 2026 20:43
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Behavior is grep-discoverable, runtime-enforced, and test-covered; the section
did not clear the bar this slim instruction file sets.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@adrian-gavrila adrian-gavrila marked this pull request as ready for review June 18, 2026 13:08
@adrian-gavrila adrian-gavrila requested a review from Copilot June 18, 2026 13:10
adrian-gavrila and others added 2 commits June 18, 2026 09:17
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…prompt' into adrian-gavrila/standardize-system-prompt

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Human review recommended

It changes a core execution chokepoint and parameter contract across many attacks, so a human reviewer should validate compatibility and any downstream behavioral impact beyond the added unit coverage.

Pull request overview

Standardizes system_prompt= as a first-class, consumed attack argument by lifting it into AttackParameters and reliably lowering it into a single leading system message at the shared AttackStrategy.execute_with_context_async entrypoint, ensuring delivery for both direct (execute_async) and executor-driven (AttackExecutor) runs.

Changes:

  • Add system_prompt: str | None to AttackParameters and lower it into context.prepended_conversation at AttackStrategy.execute_with_context_async, with a conflict ValueError when a system-role prepended message is already present.
  • Remove the dead SingleTurnAttackContext.system_prompt field and update tests to assert behavioral delivery rather than no-op state.
  • Explicitly exclude system_prompt from self-seeding / internally-constructed prompt attacks’ params_type and add unit coverage for rejection and executor-path regression.
File summaries
File Description
pyrit/executor/attack/core/attack_parameters.py Adds system_prompt to the canonical attack parameter contract.
pyrit/executor/attack/core/attack_strategy.py Lowers system_prompt into a prepended system message at the shared chokepoint and enforces conflict rules.
pyrit/executor/attack/single_turn/single_turn_attack_strategy.py Removes unused SingleTurnAttackContext.system_prompt field.
pyrit/executor/attack/single_turn/flip_attack.py Excludes system_prompt from a self-seeding attack’s accepted params.
pyrit/executor/attack/single_turn/skeleton_key.py Excludes system_prompt from a self-seeding attack’s accepted params.
pyrit/executor/attack/single_turn/many_shot_jailbreak.py Excludes system_prompt from a self-seeding attack’s accepted params.
pyrit/executor/attack/single_turn/context_compliance.py Excludes system_prompt from a self-seeding attack’s accepted params.
pyrit/executor/attack/single_turn/role_play.py Excludes system_prompt from a self-seeding attack’s accepted params.
pyrit/executor/attack/compound/sequential_attack.py Excludes system_prompt from compound attack per-call overrides.
tests/unit/executor/attack/core/test_attack_strategy.py Adds unit coverage for lowering behavior, ordering, conflict errors, and executor-bypass simulation.
tests/unit/executor/attack/core/test_attack_executor.py Regression test ensuring executor-path lowering happens via the shared chokepoint.
tests/unit/executor/attack/single_turn/test_prompt_sending.py Updates assertions to validate lowering behavior and adds delivery-to-conversation-manager test.
tests/unit/executor/attack/single_turn/test_role_play.py Verifies system_prompt exclusion from params_type and explicit rejection at runtime.

Copilot's findings

  • Files reviewed: 13/13 changed files
  • Comments generated: 0

Note

Your feedback helps us improve the quality of this feature.
Please use 👍 or 👎 to tell us whether this assessment is correct.

@adrian-gavrila adrian-gavrila changed the title [DRAFT] FEAT: Standardize system_prompt as a first-class consumed attack argument FEAT: Standardize system_prompt as a first-class consumed attack argument Jun 18, 2026
"| `objective` | What you are trying to get the **objective target** (the system under test) to do. Drives scoring and multi-turn adversarial prompts. |\n",
"| `memory_labels` | A `dict[str, str]` tagged onto every prompt/response, so you can filter this run later in memory. |\n",
"| `prepended_conversation` | A list of `Message`s to seed the conversation before the attack's own turns (system prompt, prior history). |\n",
"| `system_prompt` | The objective target's system prompt, as a string. The standard one-line way to set it; PyRIT lowers it to a single `system` message at the front of the conversation. Mutually exclusive with a `system` message in `prepended_conversation`. |\n",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the most common case BY FAR. But it's a bit complicated because there can be multiple system prompts. Different models behave differently in these cases, which is interesting to pyrit which has a goal of being flexible.

Because of that, I like it being in prepended_conversation. I think it maps more cleanly to SeedPromptAttackGroups. It is more difficult to add it when manually creating attacks, but I think SeedPrompts is the more common case.

@rlundeen2 rlundeen2 Jun 22, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In other words, my vote is to not take this change. Keep things general. And make it easy at other layers (e.g. even adding methods to the AttackExecutor is not too low)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Honestly I am wondering if the right solution here is not to simply remove the un-consumed system_prompt parameter and then document prepended_conversations as the way to add one or multiple prompts, this keeps the API surface cleaner and clarifies the correct way to add system prompts as having the unconsumed parameter was the real cause of confusion.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I went ahead with removal of the system_prompt that was dead code, and documented that using prepended_conversation is the standard way to add system prompts. Also added a helper that allows for multiple system prompts.

Comment thread pyrit/executor/attack/core/attack_parameters.py Outdated
Comment thread doc/code/executor/3_attack_configuration.py Outdated
Comment thread pyrit/executor/attack/single_turn/single_turn_attack_strategy.py
adrian-gavrila and others added 6 commits June 23, 2026 23:04
…field

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 8ae0d01b-261f-41fc-a331-179f8574732b
@adrian-gavrila adrian-gavrila changed the title FEAT: Standardize system_prompt as a first-class consumed attack argument MAINT: Standardize system prompts on prepended_conversation Jul 14, 2026
Comment thread pyrit/executor/attack/single_turn/single_turn_attack_strategy.py Outdated
Comment thread doc/code/executor/3_attack_configuration.ipynb
Comment thread pyrit/models/messages/message.py
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 8ae0d01b-261f-41fc-a331-179f8574732b
async def normalize_async(self, messages: list[Message]) -> list[Message]:
"""
Return messages with the first system message combined into the first user message.
Return messages with all system messages combined into the first user message.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if there's a system message in the middle of two user messages, I think we want it added to the second user message rather than the first. could we append the system messages to the user message following it ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants